#include "stdafx.h"
#include "Window.h"

#include "Engine.h"

namespace Cvekas {

Window::Window(HINSTANCE instace, const std::string& title, uint width, uint height, bool fullscreen)
: show_cursor(true)
{
	LOG("Creating window"); 
	this->instance = instance;
	createWindow(title, width, height, fullscreen);
}

Window::Window(HINSTANCE instace, const std::string& title)
: show_cursor(true)
{
	LOG("Creating window");
	this->instance = instance;

	Engine engine;
	VideoSettings& settings = static_cast<VideoSettings&>((*engine.getSettings())["video"]);

	createWindow(title, settings["ScreenWidth"], settings["ScreenHeight"], settings["Fullscreen"]);
}

Window::~Window()
{
	::UnregisterClass(CLASS_NAME, win_class.hInstance);
};

void Window::setTitle(const std::string& title)
{
	if(!::SetWindowText(handle, title.c_str()))
		throw EXCEPTION("Unable to set window title");
}

bool Window::update()
{
	while(::PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
	{
		::TranslateMessage(&msg);
		::DispatchMessage(&msg);
	}
	return active;
}

bool Window::isActive()
{
	return active;
}

HWND Window::getHandle()
{
	return handle;
}

// TODO: merge with createWindow, too much code duplication
void Window::switchMode()
{
	Engine engine;
	VideoSettings& new_settings = static_cast<VideoSettings&>((*engine.getSettings())["video"]);
	bool new_fullscreen = new_settings["Fullscreen"];
	uint new_width = new_settings["ScreenWidth"];
	uint new_height = new_settings["ScreenHeight"];

	if(fullscreen != new_fullscreen || width != new_width || height != new_height)
	{
		::ShowWindow(handle, SW_HIDE);
		style =  WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
		if(new_fullscreen)
		{
			style |= WS_EX_TOPMOST | WS_POPUP;
			
			::SetWindowPos(handle, HWND_TOPMOST, 0, 0, new_width, new_height, SWP_NOACTIVATE);
			if(!fullscreen)
			{
				::SetWindowLong(handle, GWL_STYLE, style);
				::SetWindowPos(handle, 0, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
			}
		}
		else
		{
			//style |= WS_SYSMENU | WS_BORDER | WS_CAPTION;
			style |= WS_OVERLAPPEDWINDOW;
			adjustWindowSize(new_width, new_height);

			::SetWindowLong(handle, GWL_STYLE, style);
			::SetWindowPos(handle, HWND_NOTOPMOST, 0, 0, new_width, new_height, 
				SWP_DRAWFRAME | SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOACTIVATE);
		}
		::ShowWindow(handle, SW_SHOW);

		fullscreen = new_fullscreen;
		width = new_width;
		height = new_height;
	}
}

void Window::createWindow(const std::string& title, uint width, uint height, bool fullscreen)
{
	win_class.cbSize = sizeof(WNDCLASSEX);
	win_class.style = CS_HREDRAW | CS_VREDRAW;
	win_class.lpfnWndProc = &Window::msgProc;
	win_class.cbClsExtra = NULL;
	win_class.cbWndExtra = NULL;
	win_class.hInstance = this->instance;
	win_class.hIcon = NULL;
	win_class.hCursor = NULL;
	win_class.hbrBackground = static_cast<HBRUSH>(::GetStockObject(WHITE_BRUSH));
	win_class.lpszMenuName = NULL;
	win_class.lpszClassName = CLASS_NAME;
	win_class.hIconSm = NULL;

	if(!::RegisterClassEx(&win_class))
		throw EXCEPTION("Unable to register window class");

	// HACK: Code duplication, can put CreateWindow after if-else
	style =  WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
	if(!fullscreen)
	{
		//DWORD style = WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_OVERLAPPED | WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
		//style |= WS_OVERLAPPED | WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
		style |= WS_OVERLAPPEDWINDOW;

		adjustWindowSize(width, height);
		handle = ::CreateWindow(CLASS_NAME, title.c_str(), style, CW_USEDEFAULT, CW_USEDEFAULT,
			width, height, NULL, NULL, this->instance, this);
	}
	else
	{
		//style |= WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_VISIBLE;
		style |= WS_EX_TOPMOST | WS_POPUP;
		handle = ::CreateWindow(CLASS_NAME, title.c_str(), style , CW_USEDEFAULT, CW_USEDEFAULT,
							width, height, NULL, NULL, this->instance, this);
	}

	if(handle == NULL)
		throw EXCEPTION("Unable to create window");

	::ShowWindow(handle, SW_SHOW);
	::UpdateWindow(handle);
	active = true;
	this->fullscreen = fullscreen;
	this->width = width;
	this->height = height;
}

LRESULT WINAPI Window::msgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{

#pragma warning(disable:4311) // pointer truncation from 'LPVOID' to 'long'	
#pragma warning(disable:4312) // conversion from 'LONG' to 'Cvekas::Window *' of greater size

	if (msg == WM_CREATE)
	{   
		// Store pointer to Window in user data area
		LPCREATESTRUCT create_struct = reinterpret_cast<LPCREATESTRUCT>(lParam);
		SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<long>(create_struct->lpCreateParams));
		return 0;
	}

	Window* window = reinterpret_cast<Window*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
	if (!window)
		return ::DefWindowProc(hWnd, msg, wParam, lParam);

#pragma warning(default:4311)
#pragma warning(default:4312)

	// Handle cursor messages before passing them to input
	switch(msg)
	{
	case WM_MOUSELEAVE:
		ShowCursor(true);
		break;
	case WM_MOUSEMOVE:
		ShowCursor(window->isCursorVisible());
		break;
	}
	
	Engine e;
	if(e.getInput())
		if(e.getInput()->process(hWnd, msg, wParam, lParam))
			return 0;

	PAINTSTRUCT ps;
    HDC hdc;

	switch(msg)
	{
		//case WM_SETCURSOR:
		//	ShowCursor(false);
		//	break;

		case WM_PAINT:
            hdc = BeginPaint( hWnd, &ps );
            EndPaint( hWnd, &ps );
            break;

		case WM_DESTROY:
			window->active = false;
			break;

		default:
			return ::DefWindowProc(hWnd, msg, wParam, lParam);
	}
	return 0;
}

void Window::adjustWindowSize(uint& width, uint& height)
{
	RECT win_rect;
	
	win_rect.top = win_rect.left = 0;
	win_rect.bottom = height;
	win_rect.right = width;
	
	::AdjustWindowRect(&win_rect, style, false);
	
	width = win_rect.right - win_rect.left;
	height = win_rect.bottom - win_rect.top;
}

} // namespace